/*
 * MineralKineticsDatabase.cpp
 *
 *  Created on: 15 Jul 2011
 *      Author: al2010
 */

#include "MineralKineticsDatabase.h"

// C++ includes
#include <cmath>
#include <iostream>
#include <fstream>
using namespace std;

// Boost includes
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>

// GeoReact includes
#include "../../Utilities/StringUtils.h"

const MineralKineticsDatabase::MineralKineticsInfoList& MineralKineticsDatabase::GetMineralKineticsInfoList() const
{
	return mineralRateInfoList;
}

const MineralKineticsDatabase& MineralKineticsDatabase::GetInstance()
{
	static MineralKineticsDatabase db;
	
	return db;
}

const MineralKineticsInfo MineralKineticsDatabase::GetMineralKineticsInfo(const string& mineralName) const
{
	const optional<MineralKineticsInfo> mineralRateInfo = FindMineralKineticsInfo(mineralName);
	
	// Check if the mineral rate information of the given species has been successfully found in the database
	if(!mineralRateInfo)
	{
		cerr << "Error: Cannot retrieve mineral rate information of the mineral " << mineralName << ". There is no such mineral in the mineral rate database." << endl; exit(0);
	}
	
	return *mineralRateInfo;
}

const optional<MineralKineticsInfo> MineralKineticsDatabase::FindMineralKineticsInfo(const string& mineralName) const
{
	auto iterMineralRateInfo = std::find_if(mineralRateInfoList.begin(), 
			                                mineralRateInfoList.end(), 
			                                [&](const MineralKineticsInfo& info) { return info.mineralName == mineralName; });
	
	if(iterMineralRateInfo != mineralRateInfoList.end())
		return optional<MineralKineticsInfo>(*iterMineralRateInfo);
	else
		return optional<MineralKineticsInfo>();
}

const double MineralKineticsDatabase::GetFromLineSpecificSurfaceArea(const string& surfAreaLine) const
{
	vector<string> splitLine = Split(surfAreaLine, " =");
	
	return atof(splitLine[1].c_str());
}

const string MineralKineticsDatabase::GetFromLineMechanismName(const string& mechanismLine) const
{
	vector<string> splitLine = Split(mechanismLine, " :");
	
	return splitLine[0];
}

const double MineralKineticsDatabase::GetFromLineRateConstant(const string& mechanismLine) const
{
	vector<string> splitLine = Split(mechanismLine, " =;");
	
	auto iter = std::find(splitLine.begin(), splitLine.end(), "log(k)"); 
	
	if(iter != splitLine.end())
		return std::pow(10.0, atof((++iter)->c_str()));
	else
		return 0.0;
}

const double MineralKineticsDatabase::GetFromLineActivationEnergy(const string& mechanismLine) const
{
	vector<string> splitLine = Split(mechanismLine, " =;");
	
	auto iter = std::find(splitLine.begin(), splitLine.end(), "Ea"); 
	
	if(iter != splitLine.end())
		return atof((++iter)->c_str());
	else
		return 0.0;
}

const vector<MineralKinetics::Catalyst> MineralKineticsDatabase::GetFromLineCatalysts(const string& mechanismLine) const
{
	vector<string> splitLine = Split(mechanismLine, " =;:{,}");
	
	auto iter = std::find(splitLine.begin(), splitLine.end(), "catalysts"); 
	
	vector<MineralKinetics::Catalyst> catalysts;
	
	if(iter != splitLine.end())
	{
		vector<string> splitSubline(++iter, splitLine.end());
		
		for(unsigned i = 0; i < splitSubline.size(); i += 3)
		{
			MineralKinetics::Catalyst catalyst;
			
			string quantityName     = splitSubline[i];
			string catalystName     = splitSubline[i + 1];
			string quantityExponent = splitSubline[i + 2];
			
			catalyst.quantityName     = quantityName;
			catalyst.quantityExponent = atof(quantityExponent.c_str());
			catalyst.catalystName     = catalystName;
			
			// Check if the 
			catalysts.push_back(catalyst);
		}
	}
	
	return catalysts;
}

const MineralKinetics::Mechanism MineralKineticsDatabase::GetFromLineMechanism(const string& mechanismLine) const
{
	MineralKinetics::Mechanism mechanism;
	
	mechanism.mechanismName = GetFromLineMechanismName(mechanismLine);
	mechanism.k             = GetFromLineRateConstant(mechanismLine);
	mechanism.Ea            = GetFromLineActivationEnergy(mechanismLine);
	mechanism.catalysts     = GetFromLineCatalysts(mechanismLine);
	
	return mechanism;
}

MineralKineticsDatabase::MineralKineticsDatabase()
{
	ifstream file("Databases/mineral.dtb");
	
	if(!file) { cout << "Error: could not open file " << "mineral.dtb" << "." << endl; exit(1); }
	
	// The String object that will hold the current line in the file to be processed 
	string line; 
	
	// Loop over all the lines of the file
	while(!file.eof())
	{
		// Get the current line of the file and clean it by removing all the left/right white spaces
		std::getline(file, line); boost::trim(line);
		
		// Check if the current line is a comment line and if so, continue in the next line
		if(line[0] == '#')
			continue;
		
		// Check if the current line begins a new mineral rate block
		if(line[0] == '-')
		{
			// The new mineral rate info to be set up
			MineralKineticsInfo mineralRateInfo;
			
			// Set the name of the mineral; note that we eliminate the first char of the line, which is '-'
			mineralRateInfo.mineralName.assign(++line.begin(), line.end());
			
			// Get the next line of the file, which should contain the specific surface area of the mineral
			std::getline(file, line);
			
			// Read the specific surface area in the current line
			mineralRateInfo.specificSurfaceArea = GetFromLineSpecificSurfaceArea(line);
			
			// Loop over all the next lines until an empty line is found
			while(true)
			{
				// Before getting a new line of the file, check if the end of file has been achieved
				if(file.eof())
					break;
				
				// Get the next line of the file, which should contain a rate mechanism or an empty line
				std::getline(file, line);
				
				// Check if the current line is an empty line, which indicates that there is no more mechanisms for the current mineral
				if(line == "")
					break;
				
				// Read the rate mechanim in the current line 
				mineralRateInfo.mechanisms.push_back( GetFromLineMechanism(line) );
			}
			
			// Add the new created mineral rate info into the container of mineral rates info
			mineralRateInfoList.push_back(mineralRateInfo);
		}
	}
}
